/*
 * UBus.cpp
 */

#include "UBus.h"

#include <string.h>
#include <iostream>
#include "json-c/json.h"

struct local_subscriber
{
    struct ubus_subscriber subscriber;
    int id;
};

// extern void uloop_disable_signals(void);

template <class P, class M> size_t coffsetof(const M P::*member)
{
    return (size_t) & (reinterpret_cast<P *>(0)->*member);
}

template <class P, class M> P *ccontainer_of(M *ptr, const M P::*member)
{
    return (P *)((char *)ptr - coffsetof(member));
}

void objects_callback(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv)
{
    std::cout << "callback called with path: " << obj->path << std::endl;
}

int local_ubus_event_handler(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method,
                             struct blob_attr *msg)
{

    struct ubus_subscriber *s;
    struct local_subscriber *sub;

    s = ccontainer_of(obj, &ubus_subscriber::obj);
    sub = ccontainer_of(s, &local_subscriber::subscriber);

    std::cout << "Got method:" << method << " id: " << sub->id << std::endl;
    char *blob = blobmsg_format_json(msg, true);
    json data;
    if (blob == nullptr)
    {
        data = json::parse("{}");
    }
    else
    {
        data = json::parse(blob);
    }
    std::cout << data.dump(4) << std::endl;
    free(blob);

    auto handler = UBusCallbackStaticManager::getCallbackHandler(sub->id);
    if (handler != nullptr)
    {
        handler->callback(data);
    }
    return 0;
}

UBus::UBus() : threadRunning{false}, signatureCallback{nullptr}, callCallback{nullptr}
{
    uloop_init();
}

UBus::~UBus()
{
    StopUloopThread();
}

void UBus::Connect()
{
    m_connectionptr = std::unique_ptr<UBusConnection>(new UBusConnection);
    m_connectionptr->ctx = ubus_connect(NULL);
    ubus_add_uloop(m_connectionptr->ctx);
    m_connectionptr->timeout = 30;
    memset(&m_connectionptr->buf, 0, sizeof(m_connectionptr->buf));
}

void UBus::Disconnect()
{
    StopUloopThread();
    ubus_free(m_connectionptr->ctx);
}

void UBus::Objects()
{
    ubus_lookup(m_connectionptr->ctx, NULL, objects_callback, this);
}

void UBus::Subscribe(std::string path, std::function<void(json)> callback)
{
    uint32_t id = 0;
    int status;
    if ((status = ubus_lookup_id(m_connectionptr->ctx, path.c_str(), &id)))
    {
        std::cout << "Unable to find: " << path << std::endl;
        return;
    }
    auto callbackHandler = std::make_shared<UBusCallback>();
    callbackHandler->callback = callback;
    UBusCallbackStaticManager::addCallbackHandler(id, callbackHandler);
    std::cout << path << " -> Id: " << id << std::endl;

    struct local_subscriber *s;

    s = static_cast<struct local_subscriber *>(malloc(sizeof(struct local_subscriber)));
    if (s == nullptr)
    {
        std::cout << "Unable to allocate memory" << std::endl;
        return;
    }
    memset(s, 0, sizeof(struct local_subscriber));

    s->subscriber.cb = local_ubus_event_handler;
    s->id = id;

    if ((status = ubus_register_subscriber(m_connectionptr->ctx, &s->subscriber)))
    {
        std::cout << "Failed to register subscriber" << std::endl;
        return;
    }

    if ((status = ubus_subscribe(m_connectionptr->ctx, &s->subscriber, id)))
    {
        std::cout << "Failed to subscribe" << std::endl;
        return;
    }

    if (!threadRunning)
    {
        StartUloopThread();
    }
}

void UBus::unSubscribe(std::string path, std::function<void(json)> callback)
{
    uint32_t id = 0;
    int status;
    if ((status = ubus_lookup_id(m_connectionptr->ctx, path.c_str(), &id)))
    {
        std::cout << "Unable to find: " << path << std::endl;
        return;
    }

    struct local_subscriber *s;

    s = static_cast<struct local_subscriber *>(malloc(sizeof(struct local_subscriber)));
    if (s == nullptr)
    {
        std::cout << "Unable to allocate memory" << std::endl;
        return;
    }
    memset(s, 0, sizeof(struct local_subscriber));

    s->subscriber.cb = local_ubus_event_handler;
    s->id = id;

    if ((status = ubus_unregister_subscriber(m_connectionptr->ctx, &s->subscriber)))
    {
        std::cout << "Failed to unregister subscriber" << std::endl;
        return;
    }

    if ((status = ubus_unsubscribe(m_connectionptr->ctx, &s->subscriber, id)))
    {
        std::cout << "Failed to unsubscribe" << std::endl;
        return;
    }
}

void UBus::StartUloopThread()
{
    std::cout << "start thread" << std::endl;
    uloopThread = std::thread([&]() {
        threadRunning = true;
        uloop_run();
        uloop_done();
        threadRunning = false;
    });
}

void UBus::StopUloopThread()
{
    uloop_end();
    if (uloopThread.joinable())
    {
        uloopThread.join();
    }
}

/***********************************************************************************************************/
notify::notify() : threadRunning{false}
{
    uloop_init();
}

notify::~notify()
{
    StopUloopThread();
}

void notify::Connect()
{
    m_connectionptr = std::unique_ptr<UBusConnection>(new UBusConnection);
    m_connectionptr->ctx = ubus_connect(NULL);
    ubus_add_uloop(m_connectionptr->ctx);
    m_connectionptr->timeout = 30;
    memset(&m_connectionptr->buf, 0, sizeof(m_connectionptr->buf));
}

void notify::Disconnect()
{
    StopUloopThread();
    ubus_free(m_connectionptr->ctx);
}

void notify::notifier(std::string path, std::string msg)
{
    uint32_t id = 0;
    int status;
    if ((status = ubus_lookup_id(m_connectionptr->ctx, path.c_str(), &id)))
    {
        std::cout << "Unable to find: " << path << std::endl;
        /* 注册一个object，client可以订阅这个object */
        status = ubus_add_object(m_connectionptr->ctx, &test_object);
        if (status)
        {
            fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
            return;
        }
    }
    ubus_notify(m_connectionptr->ctx, &test_object, msg.c_str(), NULL, -1);
}

void notify::StartUloopThread()
{
    std::cout << "start thread" << std::endl;
    uloopThread = std::thread([&]() {
        threadRunning = true;
        uloop_run();
        uloop_done();
        threadRunning = false;
    });
}

void notify::StopUloopThread()
{
    uloop_end();
    if (uloopThread.joinable())
    {
        uloopThread.join();
    }
}
